﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Management;
using System.ServiceProcess;
using System.Windows.Forms;
using System.Diagnostics;
using Microsoft.Win32;
using System.DirectoryServices.AccountManagement;
using System.Security.Cryptography;
using System.IO;

namespace PowerTriggersWcf
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together.
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
    public class Service : IPowerTriggersWcf, IPowerTriggersWcfCallback
    {
        private static IList<Action<HistoryData>> historyChangedEvents = new List<Action<HistoryData>>();

        public static void StartUp()
        {
            Application.Run(new HiddenForm());
        }

        public static void ShutDown()
        {
            SystemEvents.PowerModeChanged += new PowerModeChangedEventHandler(PowerModeChangeProcessor.SystemEvents_PowerModeChanged);
            Service.SavePendingHistory();
            Application.Exit();
        }

        public static HistoryHeader AddHistoryHeader(HistorySeverityLevels severity, string historyText)
        {
            return AddHistoryHeader(severity, historyText, true);
        }

        public static HistoryHeader AddHistoryHeader(HistorySeverityLevels severity, string historyText, bool saveData)
        {
            try
            {
                if (historyText != null)
                {
                    HistoryHeader header = new HistoryHeader(severity, DateTime.UtcNow, historyText);
                    switch (severity)
                    {
                        case HistorySeverityLevels.Informational:
                        case HistorySeverityLevels.Success:
                            EventLogHelper.LogInformation("History event: " + header.Text);
                            break;
                        case HistorySeverityLevels.Warning:
                            EventLogHelper.LogWarning("History event: " + header.Text);
                            break;
                        case HistorySeverityLevels.Error:
                            EventLogHelper.LogError("History event: " + header.Text);
                            break;
                    }

                    HistoryData.Default.AddHistory(header, saveData);

                    return header;
                }
            }
            catch (System.Exception ex)
            {
                EventLogHelper.LogError("Error occurred when attempting to add history header. The next informational application event log message states what the exception message is.", ex);
                EventLogHelper.LogInformation(historyText);
            }

            return null;
        }

        internal static HistoryHeader AddHistoryHeader(string historyText, System.Exception ex)
        {
            try
            {
                string msg = historyText.Trim() + ": ";
                if (ex == null)
                {
                    msg += "Unknown Error";
                }
                else
                {
                    msg += ex.Message.Trim() ?? string.Empty;
                    if (ex.InnerException != null && ex.InnerException.Message != ex.Message)
                    {
                        msg += ", " + ex.InnerException.Message.Trim() ?? string.Empty;
                    }
                }

                return AddHistoryHeader(HistorySeverityLevels.Error, msg);
            }
            catch (System.Exception ex2)
            {
                EventLogHelper.LogError("Error occurred when attempting to send history header exception message event. The next informational application event log message states what the exception message is.", ex2);
                EventLogHelper.LogInformation(historyText);
            }

            return null;
        }

        public static void AddHistory(HistoryHeader historyHeader, HistorySeverityLevels severity, string historyText)
        {
            AddHistory(historyHeader, severity, historyText, true);
        }

        public static void AddHistory(HistoryHeader historyHeader, HistorySeverityLevels severity, string historyText, bool saveData)
        {
            try
            {
                if (historyText != null)
                {
                    HistoryDetail item = historyHeader.AddDetail(severity, historyText.Trim());
                    switch (severity)
                    {
                        case HistorySeverityLevels.Informational:
                        case HistorySeverityLevels.Success:
                            EventLogHelper.LogInformation("History event: " + item.Text);
                            break;
                        case HistorySeverityLevels.Warning:
                            EventLogHelper.LogWarning("History event: " + item.Text);
                            break;
                        case HistorySeverityLevels.Error:
                            EventLogHelper.LogError("History event: " + item.Text);
                            break;
                    }

                    HistoryData.Default.Save(saveData);
                }

            }
            catch (System.Exception ex)
            {
                EventLogHelper.LogError("Error occurred when attempting to add history detail. The next informational application event log message states what the exception message is.", ex);
                EventLogHelper.LogInformation(historyText);
            }
        }

        internal static void AddHistory(HistoryHeader historyHeader, string historyText, System.Exception ex)
        {
            try
            {
                string msg = historyText.Trim() + ": ";
                if (ex == null)
                {
                    msg += "Unknown Error";
                }
                else
                {
                    msg += ex.Message.Trim() ?? string.Empty;
                    if (ex.InnerException != null && ex.InnerException.Message != ex.Message)
                    {
                        msg += ", " + ex.InnerException.Message.Trim() ?? string.Empty;
                    }
                }

                AddHistory(historyHeader, HistorySeverityLevels.Error, msg);
            }
            catch (System.Exception ex2)
            {
                EventLogHelper.LogError("Error occurred when attempting to send history detail exception message event. The next informational application event log message states what the exception message is.", ex2);
                EventLogHelper.LogInformation(historyText);
            }
        }

        internal static void SavePendingHistory()
        {
            HistoryData.Default.SavePendingHistory();
        }

        internal static void RaiseHistoryChangedEvent(HistoryData history)
        {
            try
            {
                if (historyChangedEvents != null)
                {
                    HistoryData threadSafeHistory;
                    IList<Action<HistoryData>> threadSafeList;
                    lock (historyChangedEvents)
                    {
                        threadSafeHistory = history.Clone();
                        threadSafeList = historyChangedEvents.ToList();
                    }
                    foreach (var action in threadSafeList)
                    {
                        try
                        {
                            action(threadSafeHistory);
                        }
                        catch (CommunicationObjectAbortedException)
                        {
                            lock (historyChangedEvents)
                            {
                                if (historyChangedEvents.Contains(action))
                                {
                                    historyChangedEvents.Remove(action);
                                }
                            }
                        }
                        catch (CommunicationObjectFaultedException)
                        {
                            lock (historyChangedEvents)
                            {
                                if (historyChangedEvents.Contains(action))
                                {
                                    historyChangedEvents.Remove(action);
                                }
                            }
                        }
                        catch (System.Exception ex)
                        {
                            EventLogHelper.LogError("RaiseHistoryChangedEvent Error", ex);
                            if (threadSafeHistory.History.Length > 0)
                            {
                                EventLogHelper.LogWarning("The following history text failed to be delivered to a subscriber: " + threadSafeHistory.History[threadSafeHistory.History.Length - 1]);
                            }
                        }
                    }
                }
            }
            catch (System.Exception)
            {
                throw;
            }
        }

        public void SubscribeHistoryChangedEvent()
        {
            IPowerTriggersEvents subscriber = OperationContext.Current.GetCallbackChannel<IPowerTriggersEvents>();
            Action<HistoryData> action = subscriber.HistoryChanged;
            lock (historyChangedEvents)
            {
                historyChangedEvents.Add(action);
            }
        }

        public void UnsubscribeHistoryChangedEvent()
        {
            IPowerTriggersEvents subscriber = OperationContext.Current.GetCallbackChannel<IPowerTriggersEvents>();
            Action<HistoryData> action = subscriber.HistoryChanged;
            lock (historyChangedEvents)
            {
                if (historyChangedEvents.Contains(action))
                {
                    historyChangedEvents.Remove(action);
                }
            }
        }

        public void RequestHistoryCallback()
        {
            try
            {
                try
                {
                    IPowerTriggersEvents subscriber = OperationContext.Current.GetCallbackChannel<IPowerTriggersEvents>();
                    subscriber.HistoryChanged(HistoryData.Default.Clone());
                }
                catch (System.Exception ex)
                {
                    EventLogHelper.LogError("RequestHistoryCallback Error", ex);
                }
            }
            catch (System.Exception)
            {
                throw;
            }
        }

        public ConfigData GetData()
        {
            return ConfigData.Default;
        }

        public bool PutData(ConfigData composite)
        {
            if (composite == null)
            {
                throw new ArgumentNullException("composite");
            }

            composite.Save();

            return true;
        }

        public void RunTest(string testType, Task[] tasks)
        {
            PowerModeChangeProcessor.AddTasksToProcessQueue(PowerTriggersWcf.PowerModeChangeProcessor.ProcessModes.Test, tasks, "Test " + testType);
            Service.SavePendingHistory();
        }

        public Task[] GetAllAvailableTasks(TaskTypes taskType)
        {
            List<Task> tasks;

            switch (taskType)
            {
                case TaskTypes.Service:
                    tasks = GetServiceList();
                    break;
                case TaskTypes.Device:
                    tasks = GetDeviceList();
                    break;
                case TaskTypes.StartProcess:
                case TaskTypes.StopProcess:
                    tasks = GetProcessList(taskType);
                    break;
                default:
                    tasks = new List<Task>();
                    break;
            }
            AddToAvailableTasks(taskType, tasks, ConfigData.Default.SuspendTasks);
            AddToAvailableTasks(taskType, tasks, ConfigData.Default.ResumeTasks);

            return tasks.ToArray();
        }

        private void AddToAvailableTasks(TaskTypes taskType, List<Task> tasks, Task[] selectedTasks)
        {
            foreach (Task task in selectedTasks.Where(t => t.TaskType == taskType))
            {
                if (!tasks.Any(t => t.ID == task.ID))
                {
                    tasks.Add(task);
                }
            }
        }

        public Task CreateTask(TaskTypes taskType
                                    , string description
                                    , string ID
                                    , string status
                                    , string category)
        {
            return Task.CreateTask(taskType, description, ID, status, category, 0, true);
        }

        public bool AuthenticateUser(ref string usernameX, string passwordX, out string errorMsg)
        {
            bool isValid = true;
            errorMsg = null;

            try
            {
                string username = usernameX.Length == 0 ? string.Empty : DecryptString(usernameX);
                string password = passwordX.Length == 0 ? string.Empty : DecryptString(passwordX);
                if (username.Length == 0)
                {
                    errorMsg = "You must enter a username";
                    isValid = false;
                }
                else
                {
                    string userPart = username;
                    string domainPart = Environment.MachineName;
                    bool isMachineName = true;
                    if (username.Contains(@"\"))
                    {
                        string[] parts = username.Split('\\');
                        if (parts.Length != 2)
                        {
                            errorMsg = "Username contains invalid characters";
                            isValid = false;
                        }
                        else
                        {
                            domainPart = parts[0];
                            userPart = parts[1];
                            if (domainPart.ToLower() != Environment.MachineName.ToLower())
                            {
                                isMachineName = false;
                            }
                        }
                    }
                    if (isValid)
                    {
                        if (username != (domainPart + @"\" + userPart))
                        {
                            username = domainPart + @"\" + userPart;
                            usernameX = EncryptString(username);
                        }

                        // create a "principal context" - e.g. your domain (could be machine, too) 
                        if (!isMachineName)
                        {
                            try
                            {
                                using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, domainPart))
                                {
                                    // validate the credentials 
                                    isValid = pc.ValidateCredentials(userPart, password);
                                }
                            }
                            catch (PrincipalServerDownException)
                            {
                                isValid = false;
                            }
                        }
                        if (!isValid)
                        {
                            using (PrincipalContext pc = new PrincipalContext(ContextType.Machine, domainPart))
                            {
                                // validate the credentials 
                                isValid = pc.ValidateCredentials(userPart, password);
                            }
                        }
                        if (!isValid)
                        {
                            errorMsg = "Error Validating User Credentials: The username and password did not authenticate successfully";
                        }
                    }
                }
            }
            catch (System.Exception ex)
            {
                errorMsg = "Error Validating User Credentials: " + ex.Message;
            }

            return isValid;
        }

        private static byte[] bytes = ASCIIEncoding.ASCII.GetBytes("FredCool");

        internal static string EncryptString(string originalString)
        {
            if (String.IsNullOrEmpty(originalString))
            {
                throw new ArgumentNullException
                       ("The string which needs to be encrypted can not be null.");
            }
            string encrpted;
            DESCryptoServiceProvider cryptoProvider = new DESCryptoServiceProvider();
            using (MemoryStream memoryStream = new MemoryStream())
            {
                using (CryptoStream cryptoStream = new CryptoStream(memoryStream, cryptoProvider.CreateEncryptor(bytes, bytes), CryptoStreamMode.Write))
                {
                    using (StreamWriter writer = new StreamWriter(cryptoStream))
                    {
                        writer.Write(originalString);
                        writer.Flush();
                        cryptoStream.FlushFinalBlock();
                        writer.Flush();
                        encrpted = Convert.ToBase64String(memoryStream.GetBuffer(), 0, (int)memoryStream.Length);
                    }
                }
            }

            return encrpted;
        }

        internal static string DecryptString(string cryptedString)
        {
            if (String.IsNullOrEmpty(cryptedString))
            {
                throw new ArgumentNullException
                   ("The string which needs to be decrypted can not be null.");
            }
            DESCryptoServiceProvider cryptoProvider = new DESCryptoServiceProvider();
            string decrypted;
            using (MemoryStream memoryStream = new MemoryStream(Convert.FromBase64String(cryptedString)))
            {
                using (CryptoStream cryptoStream = new CryptoStream(memoryStream, cryptoProvider.CreateDecryptor(bytes, bytes), CryptoStreamMode.Read))
                {
                    using (StreamReader reader = new StreamReader(cryptoStream))
                    {
                        decrypted = reader.ReadToEnd();
                    }
                }
            }

            return decrypted;
        }

        private List<Task> GetServiceList()
        {
            ServiceController[] services = ServiceController.GetServices();
            List<Task> tasks = new List<Task>(services.Length);
            int sequence = 0;
            foreach (ServiceController service in services.OrderBy(s => s.DisplayName))
            {
                Task task = new TaskService(service, sequence);
                tasks.Add(task);
                service.Dispose();
                sequence++;
            }

            return tasks;
        }

        private List<Task> GetDeviceList()
        {
            List<Task> tasks = new List<Task>();

            using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Name, Status, DeviceID, ClassGuid, Manufacturer, SystemCreationClassName, PNPDeviceID from Win32_PnpEntity"))
            {
                List<ManagementObject> deviceList = searcher.Get().OfType<ManagementObject>().OrderBy(mo => mo.GetPropertyValue("Name")).ToList();

                int sequence = 0;
                foreach (ManagementObject device in deviceList)
                {
                    Task task = new TaskDevice(device, sequence);
                    tasks.Add(task);
                    device.Dispose();
                    sequence++;
                }
            }

            return tasks;
        }

        private List<Task> GetProcessList(TaskTypes taskType)
        {
            Dictionary<string, Task> tasks = new Dictionary<string, Task>();

            IList<PWTProcess> processes = PWTProcess.GetProcesses();
            if (taskType == TaskTypes.StartProcess)
            {
                foreach (PWTProcess process in processes)
                {
                    if (!tasks.ContainsKey(process.ExePath.ToUpper()))
                    {
                        tasks.Add(process.ExePath.ToUpper(), new TaskStartProcess(process));
                    }
                    process.Dispose();
                }
            }
            else if (taskType == TaskTypes.StopProcess)
            {
                foreach (PWTProcess process in processes)
                {
                    if (!tasks.ContainsKey(process.ExePath.ToUpper()))
                    {
                        tasks.Add(process.ExePath.ToUpper(), new TaskStopProcess(process));
                    }
                    process.Dispose();
                }
            }
            
            List<Task> returnedTasks = new List<Task>();

            int sequence = 0;
            string lastDescription = string.Empty;
            foreach (var task in tasks.Values.OrderBy(t => t.Description))
            {
                if (task.Description != lastDescription)
                {
                    task.Sequence = sequence;
                    returnedTasks.Add(task);
                    sequence++;

                    lastDescription = task.Description;
                }
            }

            return returnedTasks;
        }
    }
}
